using System;
using System.Collections;
using System.IO;
using CommandLine.Utility;
using Slaks.ClusteringInfo;
using Slaks.DocumentSerialization.Dictionary;
using Slaks.DocumentSerialization.Document;
using Slaks.DocumentSerialization.Document.Wordnet;
using Slaks.DocumentSerialization.ParsedFiles;
using Slaks.VectorBasics;
using Slaks.VectorBasics.Algorithms;
using Slaks.VectorBasics.Analysis;
using Slaks.VectorBasics.Category;
using Slaks.VectorBasics.Containers;
using Slaks.VectorBasics.Dictionary;
using Slaks.VectorBasics.VectorBuilder.ExperimentalRepresentation;
using Slaks.VectorBasics.VectorBuilder.ExperimentalRepresentations;
using Slaks.Vectors.Algorithms;

namespace TestKMeans
{
	/// <summary>
	/// Summary description for Class1.
	/// </summary>
	internal class Class1
	{
		/// <summary>
		/// The main entry point for the application.
		/// </summary>
		[STAThread]
		private static void Main(string[] args)
		{
			TextWriter writer = null;
			Document2CategoryMap doc2CatMap = null;
			IList files = null;
			VectorList vectorList = new VectorList();

			// Command line parsing
			Arguments commandLine = new Arguments(args);


			const string flistParam = "fileList";
			const string dictionarySizeParam = "dictionarySize";
			const string categoryFileParam = "categoryFile";
			const string distanceAlgorithmParam = "distanceAlg";
			const string distanceAlgorithmValue2 = "COS";
			const string distanceAlgorithmValue3 = "JACAARD";
			const string iterationNumberParam = "iterationNumber";
			const string outputFileParam = "outputFile";
			const string clusterNumberParam = "clusterNumber";
			const string dictionaryParam = "dictionaryFile";
			const string dictionaryRelatedParam = "dictionaryCommand";
			const string dictionaryRelatedParamLoadValue = "Load";
			const string dictionaryRelatedParamBuildValue = "Build";
			const string dictionaryRelatedParamBuildAndQuitValue = "BuildAndQuit";
			const string clusteringInfoFileNameParam = "clusteringInfoFileName";
			const string doClusterAnalysisParam = "doClusterAnalysis";

			const string representationType = "representationType";
			const string standardRepresentation = "WordnetSentenceWeightStandard";
			const string simpleRepresentation = "WordnetSentenceWeightSimple";

			string representation = String.Empty;

			string dictionaryFile;
			string clusteringInfoFileName = String.Empty;
			string seriesFilePath;

			bool isCategory = false;
			int clusterNumber = 2;
			VectorDistanceMeasure distanceMeasureDelegate = null;
			int dictionarySizeValue = 0;
			int numberOfIterations = 1;
			int numberOfVectors;
			string dictionaryRelatedCommand;
			bool doClusterAnalysis = false;

			DictionaryMap dictionaryMap;

			try
			{
				#region output param (outputFile)

				if (commandLine[outputFileParam] == null) writer = Console.Out;
				else
				{
					try
					{
						writer = new StreamWriter(commandLine[outputFileParam], false);
					}
					catch (IOException)
					{
						if (writer != null) writer.Close();
						Console.WriteLine(commandLine[outputFileParam] + " couldn't be opened");
					}
				}

				#endregion

				#region file list param (fileList)

				if (commandLine[flistParam] != null)
				{
					seriesFilePath = commandLine[flistParam];
					files = ParsedFiles.ReadXml(seriesFilePath);
					writer.WriteLine(files.Count + " files loaded from file " + commandLine[flistParam]);
					if (writer != Console.Out)
						Console.WriteLine(files.Count + " files loaded from file " + commandLine[flistParam]);
				}
				else
				{
					writer.WriteLine("fileList parameter not found");

					if (writer != Console.Out)
						Console.WriteLine("fileList parameter not found");
					return;
				}

				#endregion

				#region dictionary size param (dictionarySize)

				if (commandLine[dictionarySizeParam] != null)
				{
					try
					{
						dictionarySizeValue = int.Parse(commandLine[dictionarySizeParam]);
					}
					catch (Exception)
					{
						writer.WriteLine("Check dictionarySize value");
						if (writer != Console.Out)
							Console.WriteLine("Check dictionarySize value");
						return;
					}
				}
				else
				{
					writer.WriteLine("No vector item pruning considered");
					if (writer != Console.Out)
						Console.WriteLine("No vector item pruning considered");
				}

				#endregion

				#region dictionary file param (dictionaryFile)

				if (commandLine[dictionaryParam] != null)
				{
					try
					{
						dictionaryFile = commandLine[dictionaryParam];
						Console.WriteLine("Dictionary filename is " + dictionaryFile);
					}
					catch (Exception ex)
					{
						writer.WriteLine(ex.Message);
						if (writer != Console.Out)
							Console.WriteLine(ex.Message + "\n" + ex.StackTrace);
						return;
					}
				}
				else
				{
					Console.WriteLine("No name for dictionary file provided");
					return;
				}

				#endregion //commented

				#region category file param (categoryFile)

				if (commandLine[categoryFileParam] != null)
				{
					doc2CatMap = CategoryReader.ReadCategoryFile(commandLine[categoryFileParam]);
					writer.WriteLine("Category file " + commandLine[categoryFileParam] + " was loaded with " + doc2CatMap.Count + " categories");
					isCategory = true;
				}
				else
				{
					writer.WriteLine("No category file was provided, clustering analysis is not possible");
					isCategory = false;
				}

				#endregion

				#region iteration number param (iterationNumber)

				if (commandLine[iterationNumberParam] != null)
				{
					try
					{
						numberOfIterations = int.Parse(commandLine[iterationNumberParam]);
						if (numberOfIterations <= 0)
						{
							numberOfIterations = 1;
							writer.WriteLine("Number of iterations is set to 1");
						}
						else
						{
							writer.WriteLine("Number of iterations is set to " + numberOfIterations);
						}
					}
					catch (Exception)
					{
						writer.WriteLine("Value of " + iterationNumberParam + " incorrectly provided (must be integer)");
						return;
					}
				}
				else
				{
					numberOfIterations = 1;
					writer.WriteLine("No number of iterations provided, default is 1");
				}

				#endregion

				#region cluster param (clusterNumber)

				if (commandLine[clusterNumberParam] != null)
				{
					try
					{
						clusterNumber = int.Parse(commandLine[clusterNumberParam]);
					}
					catch (Exception)
					{
						writer.WriteLine("Cluster number value is invalid");
						return;
					}
				}
				else
				{
					clusterNumber = 1;
					writer.WriteLine("No cluster number value specified, default is 2");
				}

				#endregion

				#region distance algorithm param

				if (commandLine[distanceAlgorithmParam] != null)
				{
					if (commandLine[distanceAlgorithmParam] == distanceAlgorithmValue2)
					{
						distanceMeasureDelegate = new VectorDistanceMeasure(VectorDistances.GetCosinus);
						Console.WriteLine("COS distance algorithm set");
						writer.WriteLine("COS distance algorithm set");
					}
					else if (commandLine[distanceAlgorithmParam] == distanceAlgorithmValue3)
					{
						Console.WriteLine("JACAARD distance algorithm set");
						writer.WriteLine("JACAARD distance algorithm set");
						distanceMeasureDelegate = new VectorDistanceMeasure(VectorDistances.GetJacaard);
					}
					else
					{
						writer.WriteLine("Uknown distance algorithm : " + commandLine[distanceAlgorithmParam] + " -> Must be [EUCL,COS,JACAARD]");
						return;
					}

				}
				else
				{
					Console.WriteLine("No distance algorithm set");
					throw new Exception("No distance algorithm set");
				}

				#endregion

				#region dictionary related parameter

				if (commandLine[dictionaryRelatedParam] == null)
				{
					writer.WriteLine("No command related to dictionary (Load,Build,BuildAndQuit)");
					throw new Exception("No command related to dictionary (Load,Build,BuildAndQuit)");
				}
				else
				{
					dictionaryRelatedCommand = commandLine[dictionaryRelatedParam];
					if (dictionaryRelatedCommand != dictionaryRelatedParamLoadValue &&
						dictionaryRelatedCommand != dictionaryRelatedParamBuildValue &&
						dictionaryRelatedCommand != dictionaryRelatedParamBuildAndQuitValue)
					{
						writer.WriteLine("Uknown dictionary related command " + dictionaryRelatedCommand);
						throw new Exception("Uknown dictionary related command " + dictionaryRelatedCommand);
					}
					Console.WriteLine("Dictionary related command is : " + dictionaryRelatedCommand);
				}

				#endregion

				#region dictionary builder

				dictionaryMap = new DictionaryMap();
				ArrayList documentList = new ArrayList();


				writer.WriteLine("Parsing started at : " + DateTime.Now.ToString());
				//get document and build dictionary
				foreach (string s in files)
				{
					WordnetDocument document = Xml2WordnetDocument.ReadXml(s);
					if (dictionaryRelatedCommand != dictionaryRelatedParamLoadValue) BuildDictionary.Build(ref dictionaryMap, dictionarySizeValue, document);
					documentList.Add(document);
				}

				if (dictionaryRelatedCommand == dictionaryRelatedParamLoadValue)
				{
					Console.WriteLine("Loading dictionary...");
					dictionaryMap = DictionaryInfo.ReadXml(dictionaryFile);
					Console.WriteLine("Dictionary was loaded");
				}

				writer.WriteLine("Dictionary size is : " + dictionaryMap.Count);
				if (writer != Console.Out) Console.WriteLine("Dictionary size is : " + dictionaryMap.Count);

				if (dictionaryRelatedCommand != dictionaryRelatedParamLoadValue)
				{
					writer.WriteLine("dictionary building complete at : " + DateTime.Now.ToString());
					if (writer != Console.Out) Console.WriteLine("dictionary building complete at : " + DateTime.Now.ToString());

					Console.WriteLine("Writing the dictionary to the xml file...");
					DictionaryInfo.XmlWrite(dictionaryFile, dictionaryMap, dictionaryMap.Count);
					Console.WriteLine("Writing the dictionary finished");
				}

				if (dictionaryRelatedCommand == dictionaryRelatedParamBuildAndQuitValue)
				{
					Console.WriteLine("Dictionary built and quit.");
					return;
				}

				#endregion

				#region clustering info filename

				if (commandLine[clusteringInfoFileNameParam] == null)
				{
					Console.WriteLine("No clustering file name provided");
					throw new Exception("No clustering file name provided");
				}
				else clusteringInfoFileName = commandLine[clusteringInfoFileNameParam];

				#endregion

				#region do cluster analysis

				if (commandLine[doClusterAnalysisParam] == null)
				{
					Console.WriteLine("No cluster analysis");
					doClusterAnalysis = false;
				}
				else
				{
					Console.WriteLine("Do cluster analysis");
					doClusterAnalysis = true;
				}

				#endregion

				#region Check categories

				//check the category
				if (doc2CatMap != null)
				{
					if (doc2CatMap.Count < vectorList.Count)
					{
						Console.WriteLine("The category mapper size is less then the number of graphs");
						throw new Exception("The category mapper size is less then the number of graphs");
					}
				}

				#endregion

				#region Representation

				if (commandLine[representationType] == standardRepresentation)
				{
					representation = standardRepresentation;
					Console.WriteLine("Representation : " + representation);
					writer.WriteLine("Representation : " + representation);
				}
					//else if (commandLine[representationType] == simpleRepresentation) representation = simpleRepresentation;
				else
				{
					Console.WriteLine("No representation or representation is not valid");
					throw new Exception("No representation or representation is not valid");
				}
				Console.WriteLine(representation + " representation");

				#endregion

				double dunnIndex;
				double mutualInfo;
				double randIndex;

				double dunnIndexAll = 0.0;
				double mutualInfoAll = 0.0;
				double randIndexAll = 0.0;

				double[] resultRand = new double[numberOfIterations];
				double[] resultDunn = new double[numberOfIterations];
				double[] resultMutual = new double[numberOfIterations];

				#region Representation build

				int skippedDocuments = 0;
				Console.WriteLine("Representation building started");

				foreach (Document document in documentList)
				{
					try
					{
						if (representation == standardRepresentation)
							vectorList.Add(StandardSentenseWeightRepresentation.Build(document, dictionaryMap));
						else
							throw new Exception("Currently only WordnetStandard representation supported");
						//vectorList.Add(SimpleRepresentation.Build(document, dictionaryMap));
					}
					catch (VectorException ex)
					{
						Console.WriteLine(ex.Message);
						skippedDocuments++;
					}
				}
				Console.WriteLine("Representation building ended");

				#endregion

				numberOfVectors = vectorList.Count - skippedDocuments;

				Console.WriteLine("number of skipped documents : " + skippedDocuments);
				writer.WriteLine("number of skipped documents : " + skippedDocuments);

				GC.Collect();

				for (int j = 1; j <= numberOfIterations; j++)
				{
					writer.WriteLine("Iteration number " + j + " : Algorithm started at : " + DateTime.Now.ToString());
					KMeans kmeans = new KMeans(vectorList, clusterNumber, distanceMeasureDelegate);
					kmeans.Apply();
					writer.WriteLine("Iteration number " + j + " : Algorithm complete at : " + DateTime.Now.ToString());

					WriteClusteringInfo(j, clusteringInfoFileName, numberOfIterations, kmeans.CountVectors(),
					                    seriesFilePath, doc2CatMap, kmeans, distanceMeasureDelegate);

					if (j == numberOfIterations)
					{
						writer.WriteLine("\n\nCluster layout for the last iteration\n");
						writer.WriteLine("Total vectors : " + kmeans.CountVectors() + "\n");
						for (int i = 0; i < kmeans.GetVectorClusters().Length; i++)
						{
							Vector centroid = kmeans.GetVectorClusters()[i].GetCentroid();
							if (centroid == null) continue;
							string name = centroid.Name;

							writer.WriteLine("Centroid : " + name + "  category : " + doc2CatMap[name]);
							foreach (Vector vector in kmeans.GetVectorClusters()[i])
							{
								name = vector.Name;
								writer.WriteLine("    " + name + "  category : " + doc2CatMap[name] + " , distance to centroid : " + distanceMeasureDelegate(centroid, vector));
							}
							writer.WriteLine("\n");
						}
					}

					//fill in categories of the graphs
					if (j == 1)
					{
						string category;
						foreach (Vector vector in vectorList)
						{
							category = doc2CatMap[vector.Name];
							vector.Category = category;
						}
					}


					if (doClusterAnalysis)
					{
						randIndex = ClusteringAnalysis.GetRandIndex(kmeans.GetVectorClusters(), null);
						dunnIndex = ClusteringAnalysis.GetDunnIndex(kmeans.GetVectorClusters(), distanceMeasureDelegate);
						mutualInfo = ClusteringAnalysis.GetMutualInformation(kmeans.GetVectorClusters(), null, CategoryReader.GetCategoryList(), numberOfVectors);

						resultRand[j - 1] = randIndex;
						resultDunn[j - 1] = dunnIndex;
						resultMutual[j - 1] = mutualInfo;

						writer.WriteLine("Rand Index = " + randIndex);
						writer.WriteLine("Dunn Index = " + dunnIndex);
						writer.WriteLine("Mutual Information = " + mutualInfo);

						randIndexAll += randIndex;
						dunnIndexAll += dunnIndex;
						mutualInfoAll += mutualInfo;
					}

					Console.WriteLine("iteration {0} ended.", j);

				}

				if (doClusterAnalysis)
				{
					if (numberOfIterations > 1)
					{
						if (randIndexAll != 0.0) randIndexAll /= numberOfIterations;
						if (dunnIndexAll != 0.0) dunnIndexAll /= numberOfIterations;
						if (mutualInfoAll != 0.0) mutualInfoAll /= numberOfIterations;

						double randDeviation = GetStandardDeviation(resultRand, randIndexAll);
						double dunnDeviation = GetStandardDeviation(resultDunn, dunnIndexAll);
						double mutualDeviation = GetStandardDeviation(resultMutual, mutualInfoAll);

						writer.WriteLine("\nAverage cluster measures");
						writer.WriteLine("Rand Index Average : " + randIndexAll);
						writer.WriteLine("Dunn Index Average : " + dunnIndexAll);
						writer.WriteLine("Mutual Information Average : " + mutualInfoAll);

						writer.WriteLine("\n");

						writer.WriteLine("Rand index standard deviation : " + randDeviation);
						writer.WriteLine("Dunn Index standard deviation : " + dunnDeviation);
						writer.WriteLine("Mutual Information standard deviation : " + mutualDeviation);

						writer.WriteLine("\n");
					}
				}

				if (isCategory)
				{
					writer.WriteLine("\nCategories : ");
					writer.WriteLine("Total categories : " + CategoryReader.GetCategoryList().Count);
					IList l = CategoryReader.GetCategoryList();
					for (int i = 0; i < l.Count; i++) writer.WriteLine(l[i]);
				}


			}
			catch (Exception ex)
			{
				writer.WriteLine(ex.Message + Environment.NewLine);
				if (ex.InnerException != null) writer.WriteLine(ex.InnerException.Message);
				writer.WriteLine(Environment.NewLine + ex.StackTrace);
				if (writer != Console.Out) Console.WriteLine(ex.Message + Environment.NewLine + ex.StackTrace);
			}
			finally
			{
				//close stream if the stream is not Console.Out
				if (commandLine[outputFileParam] != null) writer.Close();
			}
		}

		private static void WriteClusteringInfo(int iteration,
		                                        string filename,
		                                        int totalIterations,
		                                        int totalSize,
		                                        string seriespath,
		                                        Document2CategoryMap doc2map,
		                                        KMeans kmeans, VectorDistanceMeasure vectorDistanceMeasure)
		{
			string currentFileName = filename + iteration.ToString() + ".xml";
			ClusteringInfo clusteringInfo = new ClusteringInfo();
			clusteringInfo = new ClusteringInfo();
			clusteringInfo.Algorithm = "Kmeans-wordnet sentence weight";
			clusteringInfo.CategoryNumber = CategoryReader.GetCategoryList().Count;
			clusteringInfo.ClusterNumber = kmeans.GetNumberOfClusters();
			clusteringInfo.CurrentIteration = iteration;
			clusteringInfo.SeriesName = "FSeries";
			clusteringInfo.SeriesPath = seriespath;
			clusteringInfo.TotalIterations = totalIterations;
			clusteringInfo.TotalSize = totalSize;

			Cluster cluster = null;
			DocumentInfo document;
			Vector vector;
			Vector centroid;
			for (int i = 0; i < kmeans.GetNumberOfClusters(); i++)
			{
				cluster = new Cluster();
				clusteringInfo.GetClusters().Add(cluster);
				//set cluster
				document = new DocumentInfo();
				centroid = kmeans.GetVectorClusters()[i].GetCentroid();
				document.CategoryName = centroid.Category;
				document.DocumentName = centroid.Name;
				cluster.Centroid = document;

				//set documents
				for (int j = 0; j < kmeans.GetVectorClusters()[i].Count; j++)
				{
					document = new DocumentInfo();

					vector = kmeans.GetVectorClusters()[i][j];
					document.CategoryName = vector.Category;
					document.DocumentName = vector.Name;
					document.DistanceToCentroid = vectorDistanceMeasure(vector, centroid);
					cluster.GetDocuments().Add(document);
				}
			}

			ClusteringInfoSerialization.WriteXml(currentFileName, clusteringInfo);
		}

		private static double GetStandardDeviation(double[] results, double average)
		{
			double deviation = 0.0;
			for (int i = 0; i < results.Length; i++)
			{
				deviation += ((results[i] - average)*(results[i]*average));
			}

			if (results.Length == 1) return Math.Sqrt(deviation);

			deviation /= (results.Length - 1);

			deviation /= Math.Sqrt(deviation);

			return deviation;
		}


	}
}